﻿namespace CSP.Applications

module Sudoku =

    open CSP.Problem

    let private emptySudoku =
        let domain = CSP.Domain.ofSeq [1 .. 9]
        let allDomains = [ for x in 1 .. 9 do for y in 1 .. 9 do yield (x, y), domain ] |> Map.ofList
        let constr = fun (x: int) (y: int) -> x <> y

        let constraints indices =
            let a = Array.ofList indices
            [ for i in 0 .. 7 do
              for j in (i + 1) .. 8 do
              yield a.[i], constr, a.[j] ]

        let row y = [1 .. 9] |> List.map (fun x -> (x,y))
        let column x = [1 .. 9] |> List.map (fun y -> (x,y))

        let square (x,y) =
            let offsetX = 3 * x - 3
            let offsetY = 3 * y - 3
            [ for i in 1 .. 3 do
              for j in 1 .. 3 do
              yield (offsetX + i), (offsetY + j) ]

        let rows = [1 .. 9] |> List.map row
        let columns = [1 .. 9] |> List.map column
        let squares = [ for i in 1 .. 3 do for j in 1 .. 3 do yield i, j ] |> List.map square
        let allConstraints = rows |> List.append columns |> List.append squares |> List.map constraints |> List.concat

        create allDomains allConstraints

    let private addHint x y value problem =
        let domain = CSP.Domain.ofSeq [value]
        let domains = Map.add (x,y) domain problem.Domains
        create domains problem.Constraints

    /// Creates a sudoku puzzle.
    let sudoku hints = hints |> List.fold (fun p (x,y,v) -> addHint x y v p) emptySudoku

    let private transform s =
        let row y = [1 .. 9] |> List.map (fun x -> Map.find (x,y) s)
        [1 .. 9] |> List.map row

    /// Finds all solutions for a sudoku,
    let solve hints = hints |> sudoku |> CSP.Solving.simpleSolve |> Seq.map transform 

    /// Finds one solution for a sudoku.
    let find hints = hints |> sudoku |> CSP.Solving.simpleFind |> Option.map transform

    /// Prints the solution of a sudoku.
    let print s =
        let printRow r =
            List.iter (fun v -> printf "%d " v) r
            printfn ""
            printfn ""
        printfn ""
        s |> List.iter printRow
